/**
 * Copyright 2022. Huawei Technologies Co., Ltd. All rights reserved.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#include "world_ar_application.h"

#include <array>

#include <glm.hpp>
#include <gtc/matrix_transform.hpp>
#include <gtc/type_ptr.hpp>
#include <gtx/quaternion.hpp>

#include <ace/xcomponent/native_interface_xcomponent.h>

#include "world_render_manager.h"
#include "app_util.h"

namespace ArWorld {
    namespace {
        constexpr size_t K_MAX_NUMBER_OF_OBJECT_RENDERED = 10;
    }

    ArWorldApp::ArWorldApp(std::string& id) 
    : AppNapi(id)
    {
        LOGD("ArWorldApp::Constructor()");
    }

    ArWorldApp::~ArWorldApp()
    {
        LOGD("ArWorldApp::Destructor()");
        mTaskQueue.Stop();
    }

    void ArWorldApp::OnStart() {
        mTaskQueue.Start();
        mTaskQueue.Push([this] {
            // 创建一个新的AREngine_ARSession会话。
            CHECK(HMS_AREngine_ARSession_Create(nullptr, nullptr, &mArSession));
            // 配置AREngine_ARSession。
            AREngine_ARConfig *arConfig = nullptr;
            CHECK(HMS_AREngine_ARConfig_Create(mArSession, &arConfig));
            // 设置预览画面尺寸。
            CHECK(HMS_AREngine_ARConfig_SetPreviewSize(mArSession, arConfig, 1440, 1080));
            // 设置画面更新模式。
            CHECK(HMS_AREngine_ARConfig_SetUpdateMode(mArSession, arConfig, ARENGINE_UPDATE_MODE_LATEST));
            CHECK(HMS_AREngine_ARSession_Configure(mArSession, arConfig));
            HMS_AREngine_ARConfig_Destroy(arConfig);
            // 创建一个新的AREngine_ARFrame对象。
            CHECK(HMS_AREngine_ARFrame_Create(mArSession, &mArFrame));
            // 设置显示的高和宽（以像素为单位）。该高和宽是显示view的高和宽。
            CHECK(HMS_AREngine_ARSession_SetDisplayGeometry(mArSession, mDisplayRotation, mWidth, mHeight));
        });
    }

    void ArWorldApp::OnStop() {
        isPaused = true;
        mTaskQueue.Push([this] {
            LOGD("ArWorldApp::OnStop()");
            for (auto &anchor : mColoredAnchors) {
                CHECK(HMS_AREngine_ARAnchor_Detach(mArSession, anchor.anchor));
                HMS_AREngine_ARAnchor_Release(anchor.anchor);
            }
            mColoredAnchors.clear();

            if (mArFrame != nullptr) {
                HMS_AREngine_ARFrame_Destroy(mArFrame);
                mArFrame = nullptr;
            }

            if (mArSession != nullptr) {
                HMS_AREngine_ARSession_Destroy(mArSession);
                mArSession = nullptr;
            }
        });
    }

    void ArWorldApp::OnPause() {
        isPaused = true;
        mTaskQueue.Push([this] {
            CHECK(HMS_AREngine_ARSession_Pause(mArSession));
        });
    }

    void ArWorldApp::OnResume() {
        isPaused = false;
        mTaskQueue.Push([this] {
            HMS_AREngine_ARSession_Resume(mArSession);
        });
    }

    void ArWorldApp::OnUpdate()
    {
        if (isPaused) {
            LOGD("ArWorldApp::OnUpdate is paused");
            return;
        }
        mTaskQueue.Push([this] {
            if (isPaused) {
                LOGI("ArWorldApp OnDrawFrame isPaused!");
                return;
            }
            LOGD("ArWorldApp::OnDrawFrame()");
            mWorldRenderManager.OnDrawFrame(mArSession, mArFrame, mColoredAnchors);
        });
    }

    void ArWorldApp::OnSurfaceCreated(OH_NativeXComponent *component, void *window) {
        LOGD("ArWorldApp::OnSurfaceCreated()");

        int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &mWidth, &mHeight);
        LOGD("ArWorldApp::OnSurfaceCreated size (%{public}lu, %{public}lu)", mWidth, mHeight);
        if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
            ret = OH_NativeXComponent_GetXComponentOffset(component, window, &mX, &mY);
            if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
                LOGE("ArWorldApp::OnSurfaceCreated Offset : x = %{public}lf, y = %{public}lf ", mX, mY);
            }
        }
        mTaskQueue.Push([this, window] {
            LOGD("WorldRenderManager init");
            mWorldRenderManager.Initialize(window);
            CHECK(HMS_AREngine_ARSession_SetDisplayGeometry(mArSession, mDisplayRotation, mWidth, mHeight));
        });
    }

    void ArWorldApp::OnSurfaceChanged(OH_NativeXComponent *component, void *window) {
        int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &mWidth, &mHeight);
        LOGD("ArWorldApp::OnSurfaceChanged(%{public}lu, %{public}lu)", mWidth, mHeight);
        if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
            LOGD("after width = %lu, height = %lu", mWidth, mHeight);
            ret = OH_NativeXComponent_GetXComponentOffset(component, window, &mX, &mY);
            if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
                LOGD("Offset : x = %{public}lf, y = %{public}lf ", mX, mY);
            } else {
                LOGE("Offset get failed");
            }
        }

        mTaskQueue.Push([this] {
            if (isPaused) {
                LOGI("ArWorldApp OnSurfaceChanged isPaused!");
                return;
            }
            glViewport(0, 0, mWidth, mHeight);
            CHECK(HMS_AREngine_ARSession_SetDisplayGeometry(mArSession, mDisplayRotation, mWidth, mHeight));
        });
    }

    void ArWorldApp::DispatchTouchEvent(void *window, float pixeLX, float pixeLY)
    {
        // 获取命中检测结果对象列表。
        AREngine_ARHitResultList *hitResultList = nullptr;
        CHECK(HMS_AREngine_ARHitResultList_Create(mArSession, &hitResultList));
        // 射线与系统跟踪的平面，从而产生交点，形成碰撞结果。按照交点与设备的距离从近到远进行排序，存放在链表中。(eventX，eventY)是像素在预览区上坐标。
        CHECK(HMS_AREngine_ARFrame_HitTest(mArSession, mArFrame, pixeLX, pixeLY, hitResultList));
        // 获取命中检测结果对象列表中包含的对象数。
        int32_t hitResultListSize = 0;
        CHECK(HMS_AREngine_ARHitResultList_GetSize(mArSession, hitResultList, &hitResultListSize));
        LOGD("HMS_AREngine_ARHitResultList_GetSize %{public}d", hitResultListSize);
        // hitTest 方法按与相机的距离升序对结果列表进行排序。    
       // 当响应用户输入时，第一个命中结果通常是最相关的。
        AREngine_ARHitResult *arHitResult = nullptr;
        AREngine_ARTrackableType trackableType = ARENGINE_TRACKABLE_INVALID;
        bool hasHitFlag = false;
        // 获取命中点位姿信息。
        if (!GetHitResult(arHitResult, hasHitFlag, hitResultListSize, trackableType, hitResultList)) {
            return;
        }
        if (hasHitFlag != true) {
            return;
        }
        if (arHitResult) {
            // 在碰撞命中位置创建一个新的锚点。
            AREngine_ARAnchor *anchor = nullptr;
            CHECK(HMS_AREngine_ARHitResult_AcquireNewAnchor(mArSession, arHitResult, &anchor));
            // 获取指定锚点位姿的追踪状态。
            AREngine_ARTrackingState trackingState = ARENGINE_TRACKING_STATE_STOPPED;
            CHECK(HMS_AREngine_ARAnchor_GetTrackingState(mArSession, anchor, &trackingState));
            if (trackingState != ARENGINE_TRACKING_STATE_TRACKING) {
                HMS_AREngine_ARAnchor_Release(anchor);
                return;
            }
            if (mColoredAnchors.size() >= K_MAX_NUMBER_OF_OBJECT_RENDERED) {
                // 通知AREngine停止跟踪并解绑一个锚点。但是该函数并不会释放该锚点，这需要通过HMS_AREngine_ARAnchor_Release单独来实现。
                CHECK(HMS_AREngine_ARAnchor_Detach(mArSession, mColoredAnchors[0].anchor));
                HMS_AREngine_ARAnchor_Release(mColoredAnchors[0].anchor);
                mColoredAnchors.erase(mColoredAnchors.begin());
            }
            // 设置点击平面生成锚点时的颜色。
            SetAnchorColour(anchor, trackableType);
            HMS_AREngine_ARHitResult_Destroy(arHitResult);
            arHitResult = nullptr;

            HMS_AREngine_ARHitResultList_Destroy(hitResultList);
            hitResultList = nullptr;
        } else {
            LOGE("ArWorldApp::OnTouched arHitResult empty");
        }
    }
    void ArWorldApp::DispatchTouchEvent(OH_NativeXComponent *component, void *window) //OnTouched(float eventX, float eventY)
    {
        LOGD("ArWorldApp::OnTouched()");
        float pixeLX = 0.0f;
        float pixeLY = 0.0f;
        int32_t ret = OH_NativeXComponent_GetTouchEvent(component, window, &mTouchEvent);
        if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
            if (mTouchEvent.type == OH_NATIVEXCOMPONENT_DOWN) {
                pixeLX = mTouchEvent.touchPoints[0].x;
                pixeLY = mTouchEvent.touchPoints[0].y;
                LOGD("Pos: %{public}f %{public}f", pixeLX, pixeLY);
            } else {
                return;
            }
        } else {
            LOGE("Touch fail");
            return;
        }
    
        mTaskQueue.Push([this, window, pixeLX, pixeLY] {
            if (isPaused) {
                LOGI("ArWorldApp DispatchTouchEvent isPaused!");
                return;
            }
            DispatchTouchEvent(window, pixeLX, pixeLY);
        });
    }

    void ArWorldApp::OnSurfaceDestroyed(OH_NativeXComponent *component, void *window) 
    {
        LOGD("ArWorldApp::OnSurfaceDestroyed");
        mTaskQueue.Push([this] {
            LOGD("WorldRenderManager release");
            mWorldRenderManager.Release();
        });
    }


bool ArWorldApp::GetHitResult(AREngine_ARHitResult *&arHitResult, bool &hasHitFlag, int32_t hitResultListSize,
                              AREngine_ARTrackableType &trackableType,
                              AREngine_ARHitResultList *hitResultList) const {
    for (int32_t i = 0; i < hitResultListSize; ++i) {
        // 创建一个空的命中检测结果对象。
        AREngine_ARHitResult *arHit = nullptr;
        CHECK(HMS_AREngine_ARHitResult_Create(mArSession, &arHit));
        // 在命中检测结果列表中获取指定索引的命中检测结果对象。
        CHECK(HMS_AREngine_ARHitResultList_GetItem(mArSession, hitResultList, i, arHit));

        if (arHit == nullptr) {
            return false;
        }
        // 获取被命中的可追踪对象。
        AREngine_ARTrackable *arTrackable = nullptr;
        CHECK(HMS_AREngine_ARHitResult_AcquireTrackable(mArSession, arHit, &arTrackable));
        // 获取可跟踪对象的类型。
        AREngine_ARTrackableType ar_trackable_type = ARENGINE_TRACKABLE_INVALID;
        CHECK(HMS_AREngine_ARTrackable_GetType(mArSession, arTrackable, &ar_trackable_type));

        // 如果遇到平面，则会创建锚点
        if (ARENGINE_TRACKABLE_PLANE == ar_trackable_type) {
            //分配并初始化一个新的位姿对象。可以设置poseRaw为空，来创建未初始化的位姿对象。
            AREngine_ARPose *arPose = nullptr;
            CHECK(HMS_AREngine_ARPose_Create(mArSession, nullptr, 0, &arPose));
            //获取交点的位姿，其平移向量是交点在世界坐标系的坐标，其旋转分量根据碰撞点的不同类型（平面交点、点云交点）而有不同的定义。
            CHECK(HMS_AREngine_ARHitResult_GetHitPose(mArSession, arHit, arPose));
            int32_t inPolygon = 0;
            AREngine_ARPlane *arPlane = reinterpret_cast<AREngine_ARPlane *>(arTrackable);
            // 判断位姿是否位于平面的多边形范围内。0表示不在范围内，非0表示在范围内。
            CHECK(HMS_AREngine_ARPlane_IsPoseInPolygon(mArSession, arPlane, arPose, &inPolygon));
            HMS_AREngine_ARPose_Destroy(arPose);
            if (!inPolygon) {
                continue;
            }

            arHitResult = arHit;
            trackableType = ar_trackable_type;
            hasHitFlag = true;
            break;
        } 
    }
    return true;
}

    void ArWorldApp::SetAnchorColour(AREngine_ARAnchor *anchor, AREngine_ARTrackableType trackableType) {
        ColoredAnchor coloredAnchor{};
        coloredAnchor.anchor = anchor;
        switch (trackableType) {
        case ARENGINE_TRACKABLE_PLANE:
            // Set the anchor color when the anchor is generated due to click on the point cloud.
            SetColor(139.0f, 195.0f, 74.0f, 255.0f, coloredAnchor);
            break;
        default:
            // The virtual object is not displayed if it is not generated by click on the point cloud or plane.
            SetColor(0.0f, 0.0f, 0.0f, 0.0f, coloredAnchor);
            break;
        }
        mColoredAnchors.push_back(coloredAnchor);
    }

    void ArWorldApp::SetColor(float colorR, float colorG, float colorB, float colorA, ColoredAnchor &coloredAnchor)
    {
        // Set the color.
        *(coloredAnchor.color) = colorR;
        *(coloredAnchor.color + 1) = colorG;
        *(coloredAnchor.color + 2) = colorB;
        *(coloredAnchor.color + 3) = colorA;
    }
}